package io.gitee.chearnee.fw.datasecurity.core.advice;

import io.gitee.chearnee.fw.common.utils.AnnotationUtil;
import io.gitee.chearnee.fw.common.utils.BeanCloneUtil;
import io.gitee.chearnee.fw.datasecurity.core.annotation.control.DisableSD;
import io.gitee.chearnee.fw.datasecurity.core.annotation.control.EnableSDAop;
import io.gitee.chearnee.fw.datasecurity.core.common.SDAnnotationCacheUtil;
import io.gitee.chearnee.fw.datasecurity.core.executor.desensitization.SDExecutorFactory;
import lombok.SneakyThrows;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.*;

/**
 * @author cn
 * @version 1.0
 * @description
 * @date 11/15/2022 10:57 AM
 */

public class SystematicDesensitizationAop {

    /**
     * aop脱敏
     */
    @Pointcut("@annotation(io.gitee.chearnee.fw.datasecurity.core.annotation.control.EnableSDAop)" +
            "||@within(io.gitee.chearnee.fw.datasecurity.core.annotation.control.EnableSDAop)")
    public void pointcut() {
        //  document why this method is empty
    }


    @Around(value = "pointcut()")
    public Object around(ProceedingJoinPoint joinPoint) {
        return sdProcess(joinPoint);
    }

    @SneakyThrows
    private Object sdProcess(ProceedingJoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        Set<Class<?>> disabledClasses = getDisabledClasses(method);
        EnableSDAop enableSDAop = getEnableSDAop(method);
        if (enableSDAop == null) {
            return joinPoint.proceed();
        }
        Object[] args = joinPoint.getArgs();
        if (enableSDAop.in()) {
            args = parameterSDProcessor(args, method, disabledClasses);
        }
        if (enableSDAop.out()) {
            return returnSDProcessor(joinPoint.proceed(args), method, disabledClasses);
        }
        return joinPoint.proceed(args);
    }

    private Object returnSDProcessor(Object returnObj, Method method, Set<Class<?>> disabledClasses) {
        if (returnObj == null) {
            return null;
        }
        Annotation annotation = AnnotationUtil.checkOneOfAnnotation(method, SDAnnotationCacheUtil.getCacheSDAnnotations()).orElse(null);
        return SDExecutorFactory.getSDExecutor(returnObj).executeObject(returnObj, annotation, disabledClasses);
    }


    /**
     * 主要是获取参数上的注解，如果是直接传entity的，参数上不用写也可以会进行后续处理
     *
     * @param sArgs
     * @param method
     * @param disabledClasses
     * @return
     */
    private Object[] parameterSDProcessor(Object[] sArgs, Method method, Set<Class<?>> disabledClasses) {
        Object[] args = (Object[]) BeanCloneUtil.deepClone(sArgs);
        Annotation[][] params = method.getParameterAnnotations();
        List<SDEntity> sdEntities = new ArrayList<>(args.length);
        for (int i = 0; i < params.length; i++) {
            Object arg = args[i];
            Annotation[] param = params[i];
            if (arg == null) {
                SDEntity sdEntity = new SDEntity();
                sdEntity.annotation = null;
                sdEntity.obj = null;
                sdEntities.add(sdEntity);
                continue;
            }
            if (param.length == 0) {
                SDEntity sdEntity = new SDEntity();
                sdEntity.annotation = null;
                sdEntity.obj = arg;
                sdEntities.add(sdEntity);
                continue;
            }
            Optional<Annotation> first = AnnotationUtil.checkOneOfAnnotation(param, SDAnnotationCacheUtil.getCacheSDAnnotations());
            if (first.isPresent()) {
                Annotation annotation = first.get();
                SDEntity sdEntity = new SDEntity();
                sdEntity.annotation = annotation;
                sdEntity.obj = arg;
                sdEntities.add(sdEntity);
            } else {
                SDEntity sdEntity = new SDEntity();
                sdEntity.annotation = null;
                sdEntity.obj = arg;
                sdEntities.add(sdEntity);
            }
        }
        if (sdEntities.isEmpty()) {
            return sArgs;
        }
        return sdEntities.stream().map(e -> {
            if (e.obj != null) {
                e.obj = SDExecutorFactory.getSDExecutor(e.obj).executeObject(e.obj, e.annotation, disabledClasses);
            }
            return e.obj;
        }).toArray();
    }

    private EnableSDAop getEnableSDAop(Method method) {
        EnableSDAop annotation = method.getAnnotation(EnableSDAop.class);
        if (annotation == null) {
            annotation = method.getDeclaringClass().getAnnotation(EnableSDAop.class);
        }
        return annotation;
    }

    private Set<Class<?>> getDisabledClasses(Method method) {
        HashSet<Class<?>> classes = new HashSet<>();
        DisableSD disableSDClass = method.getDeclaringClass().getAnnotation(DisableSD.class);
        DisableSD disableSDMethod = method.getAnnotation(DisableSD.class);
        if (disableSDClass != null) {
            classes.addAll(Arrays.asList(disableSDClass.value()));
        }
        if (disableSDMethod != null) {
            classes.addAll(Arrays.asList(disableSDMethod.value()));
        }
        return classes;
    }


    private static class SDEntity {
        private Object obj;
        private Annotation annotation;
    }
}
